package com.alicloud.openservices.tablestore.jdbc;

import com.alicloud.openservices.tablestore.TableStoreException;
import com.alicloud.openservices.tablestore.model.sql.SQLQueryRequest;
import com.alicloud.openservices.tablestore.model.sql.SQLQueryResponse;
import com.alicloud.openservices.tablestore.model.sql.SQLStatementType;

import java.sql.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class OTSStatement extends WrapperAdapter implements Statement {

    private final OTSConnection connection;
    private long syncClientWaitFutureTimeoutInMillis;
    private int maxFieldSize = 0;
    protected int resultSetMaxRows = 0;
    private OTSResultSet resultSet;
    private boolean isClosed = false;
    private SQLWarning warning = null;

    OTSStatement(OTSConnection conn) {
        connection = conn;
        syncClientWaitFutureTimeoutInMillis = conn.config.getClientConfiguration().getSyncClientWaitFutureTimeoutInMillis();
    }

    @Override
    public void addBatch(String sql) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void cancel() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void clearBatch() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void clearWarnings() {
        warning = null;
    }

    @Override
    public void close() {
        if (!isClosed) {
            isClosed = true;
        }
    }

    @Override
    public void closeOnCompletion() {
    }

    @Override
    public int[] executeBatch() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public synchronized ResultSet executeQuery(String sql) throws SQLException {
        execute(sql);
        return getResultSet();
    }

    @Override
    public synchronized int executeUpdate(String sql) throws SQLException {
        execute(sql);
        return 0;
    }

    @Override
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean execute(String sql) throws SQLException {
        checkClosed();
        SQLQueryRequest request = new SQLQueryRequest(sql);
        try {
            Future<SQLQueryResponse> res = this.connection.otsClient.sqlQuery(request, null);
            SQLQueryResponse response = waitForFuture(res);
            if (response.getSQLStatementType() == SQLStatementType.SQL_SELECT
                    || response.getSQLStatementType() == SQLStatementType.SQL_SHOW_TABLE
                    || response.getSQLStatementType() == SQLStatementType.SQL_DESCRIBE_TABLE) {
                resultSet = new OTSResultSet(this, response.getSQLResultSet(), resultSetMaxRows);
                return true;
            } else {
                resultSet = null;
                return false;
            }
        } catch (TableStoreException e) {
            throw new SQLException(e);
        }
    }

    @Override
    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean execute(String sql, String[] columnNames) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public OTSConnection getConnection() throws SQLException {
        checkClosed();
        return connection;
    }

    @Override
    public int getFetchDirection() throws SQLException {
        checkClosed();
        return ResultSet.FETCH_FORWARD;
    }

    @Override
    public void setFetchDirection(int direction) throws SQLException {
        checkClosed();
    }

    @Override
    public int getFetchSize() throws SQLException {
        checkClosed();
        return 0;
    }

    @Override
    public void setFetchSize(int rows) throws SQLException {
        checkClosed();
    }

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public int getMaxFieldSize() {
        return maxFieldSize;
    }

    @Override
    public void setMaxFieldSize(int max) {
        maxFieldSize = max;
    }

    @Override
    public int getMaxRows() throws SQLException {
        return resultSetMaxRows;
    }

    @Override
    public void setMaxRows(int max) throws SQLException {
        checkClosed();
        if (max < 0) {
            throw new SQLException("max must be >= 0");
        }
        this.resultSetMaxRows = max;
    }

    @Override
    public boolean getMoreResults() throws SQLException {
        return false;
    }

    @Override
    public boolean getMoreResults(int current) throws SQLException {
        return false;
    }

    @Override
    public int getQueryTimeout() {
        return (int) (syncClientWaitFutureTimeoutInMillis / 1000);
    }

    @Override
    public void setQueryTimeout(int seconds) throws SQLException {
        checkClosed();
        if (seconds < 0) {
            throw new SQLException("timeout must be >= 0");
        }
        if (seconds == 0) {
            syncClientWaitFutureTimeoutInMillis = connection.config.getClientConfiguration().getSyncClientWaitFutureTimeoutInMillis();
            return;
        }
        syncClientWaitFutureTimeoutInMillis = seconds * 1000L;
    }

    @Override
    public ResultSet getResultSet() throws SQLException {
        checkClosed();
        return resultSet;
    }

    @Override
    public int getResultSetConcurrency() throws SQLException {
        return ResultSet.CONCUR_READ_ONLY;
    }

    @Override
    public int getResultSetHoldability() throws SQLException {
        return ResultSet.HOLD_CURSORS_OVER_COMMIT;
    }

    @Override
    public int getResultSetType() throws SQLException {
        return ResultSet.TYPE_FORWARD_ONLY;
    }

    @Override
    public synchronized int getUpdateCount() throws SQLException {
        if (resultSet == null) {
            return 0;
        } else {
            return -1;
        }
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return warning;
    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        return false;
    }

    @Override
    public boolean isClosed() throws SQLException {
        return isClosed;
    }

    @Override
    public boolean isPoolable() throws SQLException {
        return false;
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setCursorName(String name) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setEscapeProcessing(boolean enable) throws SQLException {
        checkClosed();
    }

    protected void checkClosed() throws SQLException {
        if (isClosed) {
            throw new SQLException("the statement has been closed");
        }
    }

    private <Res> Res waitForFuture(Future<Res> f) throws SQLException {
        try {
            return f.get(syncClientWaitFutureTimeoutInMillis, TimeUnit.MILLISECONDS);
        } catch(InterruptedException e) {
            throw new SQLException("request interrupted", e);
        } catch(ExecutionException e) {
            throw new SQLException("request aborted", e);
        } catch (TimeoutException e) {
            throw new SQLException("request timeout", e);
        }
    }
}
